package it.polito.connectedcar.tcc.tl;

import it.polito.appeal.traci.Repository;
import it.polito.appeal.traci.SumoTraciConnection;
import it.polito.appeal.traci.TrafficLight;
import it.polito.connectedcar.tcc.TCCTrafficLightAbstraction;
import it.polito.connectedcar.tcc.TCCTrafficLightPhaseChange;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * This class deals with in memory trafficLights-strcutures.It manages updates
 * coming from upper levels through "updateTrafficLight" method. Is used also
 * for information retrieval update traffic lights , like for example knowing
 * which semaphores are on a specific laneId , so that they can be given to the
 * vehicles present on that lane.
 * 
 * @author Nicola Aresta
 * 
 */
public class TCCTrafficLightsManager {

	private static TCCTrafficLightsManager tccTrafficLightsManager;

	/**
	 * Singleton trafficLightsManager
	 * 
	 * @return
	 * @throws Exception
	 */
	public static synchronized TCCTrafficLightsManager getInstance()
			throws Exception {
		if (tccTrafficLightsManager == null)
			tccTrafficLightsManager = new TCCTrafficLightsManager();
		return tccTrafficLightsManager;
	}

	private ConcurrentHashMap<String, List<TCCTrafficLightAbstraction>> trafficLights;
	private Map<String, TCCTrafficLightAbstraction> trafficLights_ids_map;
	private boolean isSetup;

	private TCCTrafficLightsManager() throws Exception {

	}

	public void setup(SumoTraciConnection conn) throws Exception {
		isSetup = true;
		trafficLights = new ConcurrentHashMap<String, List<TCCTrafficLightAbstraction>>();
		trafficLights_ids_map = new HashMap<String, TCCTrafficLightAbstraction>();
		/* retrieve the traffic lights info from SUMO */
		Repository<TrafficLight> tlRepo = conn.getTrafficLightRepository();
		Map<String, TrafficLight> tls = tlRepo.getAll();
		for (String id : tls.keySet()) {
			TrafficLight tl = tls.get(id);
			TCCTrafficLightAbstraction tccTrafficLight = new TCCTrafficLightAbstraction(tl);
			/*
			 * TODO read the traffic light position by means of GET JUNCTION
			 * variable , where the junction ID is given by the Traffic Light
			 * links internal junction--> for the time being , the position is
			 * given by our simulation (0,0
			 */
			tccTrafficLight.setPosition(new Point2D.Double(405, 405));
			trafficLights_ids_map.put(tl.getID(), tccTrafficLight);
			addTrafficLight(tccTrafficLight);
		}
	}

	/**
	 * 
	 * @param laneId
	 * @return
	 */
	public TCCTrafficLightPhaseChange getNextTrafficLightOnLane(String laneId) {
		List<TCCTrafficLightAbstraction> tls = trafficLights.get(laneId);
		if (tls == null || tls.isEmpty())
			return null;

		TCCTrafficLightPhaseChange tPhase = null;
		for (TCCTrafficLightAbstraction tl : tls) {
			tPhase = tl.getTCCTrafficLightPhaseChange(laneId);
			if (tPhase != null)
				break;
		}
		return tPhase;
	}

	/**
	 * add the traffic lights to the list of managed traffic Lights
	 * 
	 * @param tccTrafficLight
	 */
	public void addTrafficLight(TCCTrafficLightAbstraction tccTrafficLight) {
		for (String lane : tccTrafficLight.getControlledLanes()) {
			List<TCCTrafficLightAbstraction> trafficLights_ = trafficLights.get(lane);
			if (trafficLights_ == null) {
				trafficLights_ = new ArrayList<TCCTrafficLightAbstraction>();
				trafficLights.put(lane, trafficLights_);
			}
			trafficLights_.add(tccTrafficLight);
		}
	}

	public void updateTrafficLight(String trafficLightId,
			int currentPhaseIndex, long lastChangeTimestamp,
			long currentStateExpectedDuration) {
		TCCTrafficLightAbstraction tccTrafficLight = trafficLights_ids_map.get(trafficLightId);
		if (tccTrafficLight != null) {

			/*
			 * need to synchronize on the TrafficLight object to avoid
			 * inconsistency while a read is in progress
			 */
			synchronized (tccTrafficLight) {
				tccTrafficLight.setCurrentPhase(currentPhaseIndex);
				tccTrafficLight.setLastChangeTimeStamp(lastChangeTimestamp);
				tccTrafficLight.setEstimatedPhaseChangeTime(currentStateExpectedDuration);
			}
		}
	}

	public boolean isSetup() {
		return isSetup;
	}
}
